package de.jmda.mview;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import de.jmda.MarkerAnnotationType;
import de.jmda.mproc.LaunchConfiguration;
import de.jmda.mproc.Launcher;
import de.jmda.mproc.Processor;

@MarkerAnnotationType
public abstract class ElementFactory
{
	private static Map<String, TypeElement> typeElements;

	@SupportedAnnotationTypes(value = { "de.jmda.MarkerAnnotationType" })
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private static class TypeElementFactoryProcessor extends Processor
	{
		@Override
    protected boolean process()
    {
			if (roundEnvironment.processingOver() == false)
			{
				for (Element element :
				     roundEnvironment.getElementsAnnotatedWith(MarkerAnnotationType.class))
				{
					if (element instanceof TypeElement)
					{
						TypeElement typeElement = (TypeElement) element;
						getTypeElements().put(typeElement.toString(), typeElement);
					}
				}
			}
			return false;
    }

		@Override
    protected Class<?>[] getSupposedSupportedAnnotationTypes()
    {
	    return new Class<?>[] { MarkerAnnotationType.class };
    }
	}

	public static Map<String, TypeElement> getTypeElements()
	{
		if (typeElements == null)
		{
			typeElements = new HashMap<String, TypeElement>();

			LaunchConfiguration launchConfiguration = new LaunchConfiguration();

			launchConfiguration.files.addAll(getJavaFilesWithSupportedAnnotation());
			launchConfiguration.processors.add(new TypeElementFactoryProcessor());

			try
      {
	      Launcher.launch(launchConfiguration);
      }
      catch (IOException e)
      {
      	throw new ExceptionInInitializerError(
      			"failure launching configuration\n" + e);
      }
		}

		return typeElements;
	}
	
	public static TypeElement getTypeElement(Class<?> clazz)
	{
		return getTypeElements().get(clazz.getName().replaceAll("\\$", "."));
	}

	/**
	 * @return file object representing an annotated java type source file
	 */
	private static List<File> getJavaFilesWithSupportedAnnotation()
	{
		return
				Arrays.asList
				(
					new File[]
					{
						new File(
								getSourceRoot().getAbsolutePath() + "/" +
								ElementFactory.class.getPackage().getName()
								                                 .replace('.', '/') + "/" +
								ElementFactory.class.getSimpleName() + ".java")
					}
				);
	}
	
	private static File getSourceRoot()
	{
		File result = new File("./src/test/java");

		if (result.exists() == false)
		{
			throw new ExceptionInInitializerError(
					result.getAbsolutePath() + " does not exist");
		}
		else if (result.isDirectory() == false)
		{
			throw new ExceptionInInitializerError(
					result.getAbsolutePath() + " is not a directory");
		}

		return result;
	}

	@MarkerAnnotationType
	public class CPub
	{
		public final void pubF() {}
		
		protected final void profF() {}
		
		final void defF() {}
		
		private final void priF() {}
	}

	@MarkerAnnotationType
	protected class CPro {}

	@MarkerAnnotationType
	private class CPri {}

	@MarkerAnnotationType
	class CDef {}

	@MarkerAnnotationType
	public final class CPubF {}

	@MarkerAnnotationType
	protected final class CProF {}

	@MarkerAnnotationType
	private final class CPriF {}

	@MarkerAnnotationType
	final class CDefF {}

	@MarkerAnnotationType
	public final static class CPubFS {}

	@MarkerAnnotationType
	protected final static class CProFS {}

	@MarkerAnnotationType
	private final static class CPriFS {}

	@MarkerAnnotationType
	final static class CDefFS {}

	@MarkerAnnotationType
	public abstract class CPubA {}

	@MarkerAnnotationType
	protected abstract class CProA {}

	@MarkerAnnotationType
	private abstract class CPriA {}

	@MarkerAnnotationType
	abstract class CDefA {}

	@MarkerAnnotationType
	public abstract static class CPubAS {}

	@MarkerAnnotationType
	protected abstract static class CProAS {}

	@MarkerAnnotationType
	private abstract static class CPriAS {}

	@MarkerAnnotationType
	abstract static class CDefAS {}

	@MarkerAnnotationType
	public class CPubSub extends CPub {}

	@MarkerAnnotationType
	public class CPubOwner
	{
		public CPubOwned owned1;
		public CPubOwned owned2;
		
		CPubOwned[] getCPubOwned() { return null; };
		void setCPubOwned(CPubOwned[] cPubOwned) {};
	}

	@MarkerAnnotationType
	public class CPubOwned
	{
		public CPubOwned owned;
	}

	@MarkerAnnotationType
	public interface IPub
	{
	}
	
	public    void pub() {}
	protected void pro() {}
	private   void pri() {}
	          void def() {}

	public    final void pubF() {}
	protected final void proF() {}
	private   final void priF() {}
	          final void defF() {}

	public    final static void pubFS() {}
	protected final static void proFS() {}
	private   final static void priFS() {}
	          final static void defFS() {}

	public    abstract void pubA();
	protected abstract void proA();
//	private   abstract void priA(); // abstract methods can not be private
	          abstract void defA();

	//abstract methods can not be static
//	public    abstract static void pubAS();
//	protected abstract static void proAS();
//	private   abstract static void priAS();
//	          abstract static void defAS();
	
	public    int pub;
	protected int pro;
	private   int pri;
	          int def;
	
	public    final int pubF = 0;
	protected final int proF = 0;
	private   final int priF = 0;
	          final int defF = 0;

	public    final static int pubFS = 0;
	protected final static int proFS = 0;
	private   final static int priFS = 0;
	          final static int defFS = 0;
}